package com.yeskery.nut.cloud.feign;

import com.yeskery.nut.annotation.cloud.feign.EnableFeignClients;
import com.yeskery.nut.annotation.cloud.feign.FeignClient;
import com.yeskery.nut.annotation.web.*;
import com.yeskery.nut.bean.*;
import com.yeskery.nut.util.ClassLoaderUtils;
import com.yeskery.nut.util.ClassUtils;
import com.yeskery.nut.util.ReflectUtils;
import com.yeskery.nut.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.*;

/**
 * feign客户端import注解注册器
 * @author YESKERY
 * 2024/1/9
 */
public class FeignClientsImportBeanRegistrar implements ImportBeanRegistrar {
    @Override
    public void registerBeans(ImportAnnotationMetadata importAnnotationMetadata, BeanRegister beanRegister) {
        List<EnableFeignClients> enableFeignClients = getEnableFeignClients(importAnnotationMetadata);

        Set<String> basePackages = new HashSet<>();
        Set<Class<?>> clientClasses = new HashSet<>();
        for (EnableFeignClients enableFeignClient : enableFeignClients) {
            for (String basePackage : enableFeignClient.basePackages()) {
                if (!StringUtils.isEmpty(basePackage)) {
                    basePackages.add(basePackage);
                }
            }
            for (Class<?> clazz : enableFeignClient.basePackageClasses()) {
                if (clazz.getPackage() != null) {
                    basePackages.add(clazz.getPackage().getName());
                }
            }
            for (Class<?> clazz : enableFeignClient.clients()) {
                if (isFeignClientClass(clazz)) {
                    clientClasses.add(clazz);
                }
            }
        }

        List<Class<?>> feignClientClassList = new ArrayList<>();
        for (String basePackage : basePackages) {
            ClassUtils.obtainClassListByFilter(basePackage,
                    cls -> isFeignClientClass(cls) && (clientClasses.isEmpty() || clientClasses.contains(cls)) ? FeignClient.class : null,
                    (cls, clazz) -> feignClientClassList);
        }

        for (Class<?> feignClass : feignClientClassList) {
            FeignClientMetadata feignClientMetadata = new FeignClientMetadata();
            feignClientMetadata.setType(feignClass);
            FeignClient feignClient = feignClass.getAnnotation(FeignClient.class);
            feignClientMetadata.setFeignClient(feignClient);

            Method[] declaredMethods = feignClass.getDeclaredMethods();
            Map<String, FeignRequestMetadata> feignRequestMetadataMap = new HashMap<>(declaredMethods.length);
            for (Method method : declaredMethods) {
                FeignRequestMetadata feignRequestMetadata = parseFeignRequestMetadataFromMethod(feignClient, method);
                if (feignRequestMetadata != null) {
                    feignRequestMetadataMap.put(ReflectUtils.getMethodUniqueName(method), feignRequestMetadata);
                }
            }

            Object proxyBean = Proxy.newProxyInstance(ClassLoaderUtils.getClassLoader(), new Class[]{feignClass},
                    new FeignClientProxyInvocationHandler((ApplicationContext) beanRegister, feignClientMetadata, feignRequestMetadataMap));
            ((FactoryBeanRegister) beanRegister).registerBean(ReflectUtils.getDefaultBeanName(feignClass), proxyBean, feignClass);
        }
    }

    /**
     * 判断是否是{@link FeignClient}注解所标注的类对象
     * @param clazz 要判断的类
     * @return {@link FeignClient}注解所标注的类对象
     */
    private boolean isFeignClientClass(Class<?> clazz) {
        return clazz != null && clazz.isInterface() && clazz.isAnnotationPresent(FeignClient.class);
    }

    /**
     * 获取{@link EnableFeignClients}注解对象
     * @param importAnnotationMetadata {@link com.yeskery.nut.annotation.bean.Import}注解的注解元数据
     * @return {@link EnableFeignClients}注解对象
     */
    private List<EnableFeignClients> getEnableFeignClients(ImportAnnotationMetadata importAnnotationMetadata) {
        List<EnableFeignClients> enableFeignClients = new ArrayList<>();
        for (ImportAnnotationTypeMetadata metadata : importAnnotationMetadata.getMetadata()) {
            for (ClassAndAnnotation classAndAnnotation : metadata.getClassAndAnnotations()) {
                if (classAndAnnotation.getAnnotation() instanceof EnableFeignClients) {
                    enableFeignClients.add((EnableFeignClients) classAndAnnotation.getAnnotation());
                }
            }
        }
        return enableFeignClients;
    }

    /**
     * 从方法中转换feign请求元数据对象
     * @param feignClient feign客户端注解
     * @param method 方法对象
     * @return 转换后的feign请求元数据对象
     */
    private FeignRequestMetadata parseFeignRequestMetadataFromMethod(FeignClient feignClient, Method method) {
        if (!method.isAnnotationPresent(GetMapping.class) && !method.isAnnotationPresent(PostMapping.class)
                && !method.isAnnotationPresent(PutMapping.class) && !method.isAnnotationPresent(DeleteMapping.class)) {
            return null;
        }
        String baseUri = feignClient.path();
        String path;
        com.yeskery.nut.core.Method requestMethod;
        String[] consumes, produces;
        if (method.isAnnotationPresent(GetMapping.class)) {
            GetMapping getMapping = method.getAnnotation(GetMapping.class);
            path = getRequestPath(getMapping.value(), getMapping.path());
            requestMethod = com.yeskery.nut.core.Method.GET;
            consumes = getMapping.consumes();
            produces = getMapping.produces();
        } else if (method.isAnnotationPresent(PostMapping.class)) {
            PostMapping postMapping = method.getAnnotation(PostMapping.class);
            path = getRequestPath(postMapping.value(), postMapping.path());
            requestMethod = com.yeskery.nut.core.Method.POST;
            consumes = postMapping.consumes();
            produces = postMapping.produces();
        } else if (method.isAnnotationPresent(PutMapping.class)) {
            PutMapping putMapping = method.getAnnotation(PutMapping.class);
            path = getRequestPath(putMapping.value(), putMapping.path());
            requestMethod = com.yeskery.nut.core.Method.PUT;
            consumes = putMapping.consumes();
            produces = putMapping.produces();
        } else if (method.isAnnotationPresent(DeleteMapping.class)){
            DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
            path = getRequestPath(deleteMapping.value(), deleteMapping.path());
            requestMethod = com.yeskery.nut.core.Method.DELETE;
            consumes = deleteMapping.consumes();
            produces = deleteMapping.produces();
        } else {
            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
            com.yeskery.nut.core.Method[] methods;
            if (requestMapping == null || (methods = requestMapping.method()).length == 0) {
                return null;
            }
            path = getRequestPath(requestMapping.value(), requestMapping.path());
            requestMethod = methods[0];
            consumes = requestMapping.consumes();
            produces = requestMapping.produces();
        }
        if (path == null) {
            return null;
        }
        path = baseUri + (path.startsWith("/") ? path : "/" + path);
        FeignRequestMetadata feignRequestMetadata = new FeignRequestMetadata();
        feignRequestMetadata.setName(feignClient.name());
        feignRequestMetadata.setUrl(path);
        feignRequestMetadata.setMethod(requestMethod);
        feignRequestMetadata.setConsumes(consumes);
        feignRequestMetadata.setProduces(produces);
        feignRequestMetadata.setRequestAnnotationTypeMap(getAnnotationRequestMethodAttributes(method));
        feignRequestMetadata.setReturnType(method.getReturnType());
        feignRequestMetadata.setGenericReturnType(method.getGenericReturnType());
        return feignRequestMetadata;
    }

    /**
     * 获取注解请求方法属性
     * @param method 处理方法
     * @return 注解请求方法属性
     */
    private Map<Integer, RequestAnnotationType> getAnnotationRequestMethodAttributes(Method method) {
        Map<Integer, RequestAnnotationType> requestAnnotationTypeMap = new HashMap<>(16);
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            RequestBody requestBody = parameter.getAnnotation(RequestBody.class);
            if (requestBody != null) {
                requestAnnotationTypeMap.put(i, new RequestAnnotationType(RequestBody.class, requestBody));
            }

            RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
            if (requestParam != null) {
                requestAnnotationTypeMap.put(i, new RequestAnnotationType(RequestParam.class, requestParam));
            }

            CookieValue cookieValue = parameter.getAnnotation(CookieValue.class);
            if (cookieValue != null) {
                requestAnnotationTypeMap.put(i, new RequestAnnotationType(CookieValue.class, cookieValue));
            }

            RequestHeader requestHeader = parameter.getAnnotation(RequestHeader.class);
            if (requestHeader != null) {
                requestAnnotationTypeMap.put(i, new RequestAnnotationType(RequestHeader.class, requestHeader));
            }

            PathVariable pathVariable = parameter.getAnnotation(PathVariable.class);
            if (pathVariable != null) {
                requestAnnotationTypeMap.put(i, new RequestAnnotationType(PathVariable.class, pathVariable));
            }
        }
        return requestAnnotationTypeMap;
    }

    /**
     * 获取请求参数
     * @param paths 路径
     * @param alias 别名
     * @return 请求参数
     */
    private String getRequestPath(String[] paths, String[] alias) {
        if (paths.length > 0) {
            return paths[0];
        }
        if (alias.length > 0) {
            return alias[0];
        }
        return null;
    }
}
